# ga_optimization_csv.py
# 一次性脚本：遗传算法优化 + 纯 CSV 输出
# 去掉了 openpyxl，所有 Excel 操作改为 CSV

import csv
import datetime
import os
from pathlib import Path

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.ticker import MaxNLocator
from sko.GA import GA
from sko.tools import set_run_mode

# 导入自定义模块与常量
from osc05.optimization.get_cost import get_cost_with_simulation, get_sampled_duration_and_delivery_time
from osc05.static.constants import Constants, Debugs, DirectoryConfig
from osc05.utils.random_util import RandomUtil

# -------------------------- 辅助函数 --------------------------
def fitness_func(start_correction):
    """适应度函数：计算延迟方案的总成本"""
    return get_cost_with_simulation(
        start_correction=start_correction,
        seed=Constants.SEED,
        unit_waiting_penalty=Debugs.unit_component_late_acceptance_penalty_by_day,
        unit_late_delivery_penalty=Debugs.unit_late_delivery_penalty_by_day,
        unit_project_late_completion_penalty=Debugs.unit_project_late_completion_penalty_by_day,
        project_deadline=Debugs.project_deadline
    )


def visualize(best_x, best_y, clap, plcp):
    """可视化并保存图片（同时弹窗）"""
    plt.ioff()
    y_history = pd.DataFrame(ga.all_history_Y)
    plt.rcParams['font.family'] = 'Arial'
    fig, ax = plt.subplots(2, 1, figsize=(10, 8), dpi=150)

    # 迭代曲线
    ax[0].plot(y_history.index, y_history.values, '.', color='blue')
    ax[0].set_xlabel('Generation')
    ax[0].set_ylabel('Penalty value')
    ax[0].set_title(
        f"GA iteration\npop={Debugs.size_pop}, max_iter={Debugs.max_iter}, best={round(best_y[0], 2)}"
    )

    # 延迟柱状图
    ax[1].bar(range(1, Constants.N_ACTIVITY + 1), np.array(best_x), color='blue')
    ax[1].set_ylim(0, 7)
    ax[1].set_xlabel(f'Activities\n\nCLAP={clap}  PLCP={plcp}')
    ax[1].set_ylabel('Postponement / day')
    ax[1].set_title("Suggested postponements")
    ax[1].xaxis.set_major_locator(MaxNLocator(integer=True))
    ax[1].yaxis.set_major_locator(MaxNLocator(integer=True))

    # 保存图片
    filename = os.path.join(
        DirectoryConfig.OUTPUT_DIR,
        f'Optimization_CLAP{clap:04d}_PLCP{plcp:05d}.png'
    )
    fig.savefig(filename, dpi=150)

    # 弹窗（阻塞）
    plt.show(block=True)

def get_postponement_cost(x):
    """返回成本明细"""
    a, b, c = get_cost_with_simulation(
        start_correction=x,
        seed=Constants.SEED,
        unit_waiting_penalty=Debugs.unit_component_late_acceptance_penalty_by_day,
        unit_late_delivery_penalty=Debugs.unit_late_delivery_penalty_by_day,
        unit_project_late_completion_penalty=Debugs.unit_project_late_completion_penalty_by_day,
        project_deadline=Debugs.project_deadline,
        debug=True
    )
    total = a - b + c
    return a, b, c, total


# -------------------------- 主程序 --------------------------
if __name__ == '__main__':

    # 1. 目录准备
    DirectoryConfig.ensure_all_dirs_exist()

    # 2. 初始化汇总文件
    summary_path = os.path.join(DirectoryConfig.OUTPUT_DIR, 'summary.csv')
    with open(summary_path, 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(['type', 'seed', 'CLAP', 'PLCP', 'total_cost'] +
                        [f'C{i}' for i in range(1, Constants.N_ACTIVITY + 1)])

    # 3. 初始化敏感性分析文件
    sensitivity_path = os.path.join(DirectoryConfig.OUTPUT_DIR, 'sensitivity_table.csv')
    with open(sensitivity_path, 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerow([
            'Unit CLAP/day [$]', 'Unit PLCP/day [$]', 'Ratio PLCP/CLAP',
            'Avg. TAC of B.S. [$]', 'Avg. CLAP of B.S. [$]',
            'Avg. CLDP of B.S. [$]', 'Avg. PLCP of B.S. [$]',
            'Avg. suggested postponement [day]', 'Avg. reduced TAC [$]',
            'Avg. reduced CLAP [$]', 'Avg. reduced CLDP [$]',
            'Avg. reduced PLCP [$]', 'Reduction rate'
        ])

    # 4. 随机工具
    samples_dir = Path(__file__).parent.parent / "sampling" / "samples"
    random_util = RandomUtil(seed=1, n_sf=Constants.N_ACTIVITY, samples_dir=samples_dir)

    # 5. 主循环
    for clap in [1000]:
        for plcp in [5000]:
            Debugs.unit_component_late_acceptance_penalty_by_day = clap
            Debugs.unit_project_late_completion_penalty_by_day = plcp
            prob_mut = 1e-06

            # 5.1 该参数组合的单独 CSV
            plan_filename = os.path.join(
                DirectoryConfig.OUTPUT_DIR,
                f'receiving_plan_CLAP{clap}_PLCP{plcp}.csv'
            )
            with open(plan_filename, 'w', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(['meta', 'size_pop', Debugs.size_pop,
                                 'max_iter', Debugs.max_iter, 'ub', 7])
                header = ['type', 'seed', 'CLAP', 'PLCP', 'total_cost'] + \
                         [f'C{i}' for i in range(1, Constants.N_ACTIVITY + 1)]
                writer.writerow(header)

            set_run_mode(fitness_func, 'cached')

            ga = GA(
                func=fitness_func,
                n_dim=Constants.N_ACTIVITY,
                size_pop=Debugs.size_pop,
                max_iter=Debugs.max_iter,
                prob_mut=prob_mut,
                lb=[0] * Constants.N_ACTIVITY,
                ub=[7] * Constants.N_ACTIVITY,
                precision=1
            )

            # 初始种群：二进制编码（7 用 3 位）
            init_pop = np.random.randint(0, 2, size=(Debugs.size_pop, Constants.N_ACTIVITY * 3))
            # 第 0 条染色体设为全 0（即延迟全 0）
            init_pop[0] = 0
            ga.Chrom = init_pop

            start_time = datetime.datetime.now()
            best_x, best_y = ga.run()
            lapse = (datetime.datetime.now() - start_time).total_seconds()

            act_delivery_delay, durations = get_sampled_duration_and_delivery_time(
                seed=Constants.SEED,
                random_util=random_util
            )

            # 基线成本（全 0）
            base_total = get_cost_with_simulation(
                start_correction=[0] * Constants.N_ACTIVITY,
                seed=Constants.SEED,
                unit_waiting_penalty=clap,
                unit_late_delivery_penalty=Debugs.unit_late_delivery_penalty_by_day,
                unit_project_late_completion_penalty=plcp,
                project_deadline=Debugs.project_deadline
            )

            # 明细
            clap_best, cldp_best, plcp_best, total_best = get_postponement_cost(best_x)
            clap_base, cldp_base, plcp_base, total_base = get_postponement_cost([0] * Constants.N_ACTIVITY)

            # 5.2 写单独 CSV
            with open(plan_filename, 'a', newline='') as f:
                w = csv.writer(f)
                w.writerow(['base_cost', Constants.SEED, clap, plcp, base_total] + durations)
                w.writerow(['delivery_delay', Constants.SEED, clap, plcp, ''] + [''] + act_delivery_delay)
                w.writerow(['lapse', lapse])
                w.writerow(['min_cost', Constants.SEED, clap, plcp, total_best] + list(best_x))
                w.writerow(['cost_detail', 'total_cost', total_best,
                            'CLAP', round(clap_best, 2),
                            'CLDP', round(cldp_best, 2),
                            'PLCP', round(plcp_best, 2)])

            # 5.3 写 summary.csv
            with open(summary_path, 'a', newline='') as f:
                w = csv.writer(f)
                w.writerow(['base_cost', Constants.SEED, clap, plcp, base_total] + durations)
                w.writerow(['min_cost', Constants.SEED, clap, plcp, total_best] + list(best_x))
                w.writerow(['cost_detail', Constants.SEED, clap, plcp,
                            total_best, clap_best, cldp_best, plcp_best])

            # 5.4 写敏感性表
            with open(sensitivity_path, 'a', newline='') as f:
                csv.writer(f).writerow([
                    clap, plcp, plcp / clap,
                    total_base, clap_base, cldp_base, plcp_base,
                    round(sum(best_x) / len(best_x), 2),
                    total_best, clap_best, cldp_best, plcp_best,
                    round((total_base - total_best) / total_base, 2)
                ])

            # 5.5 可视化
            visualize(best_x, best_y, clap, plcp)

    print("End of optimization")